﻿using System;
using System.Collections.Generic;
using System.Linq;
using MathService;
using MathService.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MathServiceTestProject
{
    [TestClass]
    public class Polynom_Test : ModuleTest
    {
        public TestContext TestContext { get; set; }

        #region Additional test attributes

        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize]
        //public static void MyClassInitialize(TestContext testContext) { }

        //[ClassCleanup]
        //public static void MyClassCleanup() { }

        //[TestInitialize]
        //public void MyTestInitialize() { }

        //[TestCleanup]
        //public void MyTestCleanup() { }

        #endregion

        /// <summary>Тест конструктора</summary>
        [TestMethod, Priority(1), Timeout(100), Description("Тест конструктора")]
        public void PolynomConstructorTest()
        {
            var N = GetRandomIntMinMax(5, 15);
            var A = GetRndDoubleVector(N);
            var polynom = new Polynom(A);
            Assert.AreEqual(A.Length - 1, polynom.Power,
                "Степень полинома {0} не соответствует числу коэффициентов при степенях {1} - 1", polynom.Power, A.Length);

            for(var i = 0; i < N; i++)
                Assert.AreEqual(A[i], polynom[i], "Коэффициент {0} при степени {1} не соответствует исходному {2}",
                    polynom[i], i, A[i]);

            polynom = new Polynom(new List<double>(A));
            Assert.AreEqual(A.Length - 1, polynom.Power,
               "Степень полинома {0} не соответствует числу коэффициентов при степенях {1} - 1", polynom.Power, A.Length);
            for(var i = 0; i < N; i++)
                Assert.AreEqual(A[i], polynom[i], "Коэффициент {0} при степени {1} не соответствует исходному {2}",
                    polynom[i], i, A[i]);
        }

        /// <summary>Тест значения полинома</summary>
        [TestMethod, Priority(1), Timeout(100), Description("Тест значения")]
        public void ValueTest()
        {
            var A = new double[] { 3, 5, 7 };
            var p = new Polynom(A);
            Assert.AreEqual(3, p.Value(0));
            Assert.AreEqual(15, p.Value(1));
            Assert.AreEqual(41, p.Value(2));
            Assert.AreEqual(81, p.Value(3));

            var N = ModuleTest.GetRandomIntMinMax(5, 15);
            A = ModuleTest.GetRndDoubleVector(N);
            p = new Polynom(A);
            var X = ModuleTest.GetRndDoubleVector(ModuleTest.GetRandomIntMinMax(5, 15));

            Func<double, double> p1 = x =>
            {
                var P = 0.0;
                for(var i = 0; i < N; i++)
                    P += A[i] * Math.Pow(x, i);
                return P;
            };

            X.Foreach(x =>
                          {
                              var actual = p.Value(x);
                              var expected = p1(x);
                              Assert.AreEqual(expected, actual, 1e-15,
                                    "Разница между полученным {0} и ожидаемым {1} значениями полинома привысила 1e-15",
                                    actual, expected);
                          });

        }

        ///// <summary>
        ///// Тест клонирования
        ///// </summary>
        //[TestMethod]
        //[Description("Тест клонирования")]
        //public void CloneTest()
        //{
        //    double[] a = null; // TODO: Initialize to an appropriate value
        //    var target = new Polynom(a); // TODO: Initialize to an appropriate value
        //    object expected = null; // TODO: Initialize to an appropriate value
        //    var actual = target.Clone();
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        /// <summary>Тестирование метода определения равенства полиномов</summary>
        [TestMethod, Priority(1), Description("Тестирование метода определения равенства полиномов")]
        public void EqualsTest()
        {
            var P = new Polynom(1, 3, 5);
            var Q = new Polynom(1, 3, 5);
            var Z = new Polynom(1, 3, 5, 7);

            Assert.IsTrue(P.Equals(P), "Полином {0} не равен сам себе {1}", P, P);
            Assert.IsTrue(P.Equals(Q), "Полином {0} не равен идентичному полиному {1}", P, Q);
            Assert.IsFalse(P.Equals(null), "Полином {0} равен null", P);
            Assert.IsFalse(P.Equals(Z), "Полином {0} равен неравному ему полиному {1}", P, Z);

            P = new Polynom(ModuleTest.GetRndDoubleVector(sigma: 10));
            Q = new Polynom(P.Coefficients);

            Assert.IsTrue(P.Equals(Q), "Случайный полином {0} не равен полиному {1}, составленному из его коэффициентов", P, Q);
            Assert.IsFalse(P.Equals(Z), "Случайный полином {0} равен неравному ему полиному {1}", P, Z);
        }

        /// <summary>Тестирование метода определения равенства полиномов</summary>
        [TestMethod, Priority(1), Description("Тестирование метода определения равенства полинома объекту")]
        public void EqualsTest1()
        {
            var P = new Polynom(1, 3, 5);
            var Q = new Polynom(1, 3, 5);
            var Z = new Polynom(1, 3, 5, 7);

            Assert.IsTrue(P.Equals((object)P), "Полином {0} не равен сам себе {1}", P, P);
            Assert.IsTrue(P.Equals((object)Q), "Полином {0} не равен идентичному полиному {1}", P, Q);
            Assert.IsFalse(P.Equals((object)null), "Полином {0} равен null", P);
            Assert.IsFalse(P.Equals(new object()), "Полином {0} равен null", P);
            Assert.IsFalse(P.Equals(5), "Полином {0} равен целому числу", P);
            Assert.IsFalse(P.Equals("Test"), "Полином {0} равен строке", P);
            Assert.IsFalse(P.Equals((object)Z), "Полином {0} равен неравному ему полиному {1}", P, Z);

            P = new Polynom(ModuleTest.GetRndDoubleVector(sigma: 10));
            Q = new Polynom(P.Coefficients);

            Assert.IsTrue(P.Equals((object)Q), "Случайный полином {0} не равен полиному {1}, составленному из его коэффициентов", P, Q);
            Assert.IsFalse(P.Equals((object)Z), "Случайный полином {0} равен неравному ему полиному {1}", P, Z);
        }

        ///// <summary>
        ///// A test for GetHashCode
        ///// </summary>
        //[TestMethod]
        //public void GetHashCodeTest()
        //{
        //    double[] a = null; // TODO: Initialize to an appropriate value
        //    Polynom target = new Polynom(a); // TODO: Initialize to an appropriate value
        //    int expected = 0; // TODO: Initialize to an appropriate value
        //    int actual;
        //    actual = target.GetHashCode();
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        ///// <summary>
        ///// A test for GetPolynom
        ///// </summary>
        //[TestMethod]
        //public void GetPolynomTest()
        //{
        //    double[] Root = null; // TODO: Initialize to an appropriate value
        //    Polynom expected = null; // TODO: Initialize to an appropriate value
        //    Polynom actual;
        //    actual = Polynom.GetPolynom(Root);
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        ///// <summary>
        ///// A test for GetPowers
        ///// </summary>
        //[TestMethod]
        //public void GetPowersTest()
        //{
        //    double[] Root = null; // TODO: Initialize to an appropriate value
        //    double[] expected = null; // TODO: Initialize to an appropriate value
        //    double[] actual;
        //    actual = Polynom.GetPowers(Root);
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        /// <summary>Тест оператора сложения полиномов</summary>
        [TestMethod]
        public void op_AdditionTest()
        {
            var P = new Polynom(3, 5, 7);
            var Q = new Polynom(9, 8, 15, 23);
            var Z = new Polynom(12, 13, 22, 23);

            Assert.IsTrue((P + Q).Equals(Z), "Сумма детерминированных тестовых полиномов расчитана неверно: " +
                                             "{0} + {1} == {2}", P, Q, Z);

            P = GetRandomPolynom();
            Q = GetRandomPolynom();

            var X = ModuleTest.GetRndDoubleVector(sigma: 10);

            var yP = X.Select(P.Value);
            var yQ = X.Select(Q.Value);

            Z = P + Q;

            var Y_actual = X.Select(Z.Value);
            var Y_expected = yP.Zip(yQ, (a, b) => a + b);

            var Y = Y_actual.Zip(Y_expected, (actual, expected) => new { actual, expected });
            Y.Foreach(v => Assert.IsFalse(Math.Abs(v.expected - v.actual) / v.expected > 4.45e-14,
                "Относительная точность между ожидаемым {0} и полученным {1} значениями составила {2}",
                v.expected, v.actual, Math.Abs(v.expected - v.actual) / v.expected));
        }

        ///// <summary>
        ///// A test for op_Implicit
        ///// </summary>
        //[TestMethod]
        //public void op_ImplicitTest()
        //{
        //    Polynom P = null; // TODO: Initialize to an appropriate value
        //    Converter<double, double> expected = null; // TODO: Initialize to an appropriate value
        //    Converter<double, double> actual;
        //    actual = P;
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        ///// <summary>
        ///// A test for Coefficients
        ///// </summary>
        //[TestMethod]
        //public void CoefficientsTest()
        //{
        //    double[] a = null; // TODO: Initialize to an appropriate value
        //    Polynom target = new Polynom(a); // TODO: Initialize to an appropriate value
        //    double[] expected = null; // TODO: Initialize to an appropriate value
        //    double[] actual;
        //    target.Coefficients = expected;
        //    actual = target.Coefficients;
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        ///// <summary>
        ///// A test for Item
        ///// </summary>
        //[TestMethod]
        //public void ItemTest()
        //{
        //    double[] a = null; // TODO: Initialize to an appropriate value
        //    Polynom target = new Polynom(a); // TODO: Initialize to an appropriate value
        //    int i = 0; // TODO: Initialize to an appropriate value
        //    double expected = 0F; // TODO: Initialize to an appropriate value
        //    double actual;
        //    target[i] = expected;
        //    actual = target[i];
        //    Assert.AreEqual(expected, actual);
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        ///// <summary>
        ///// A test for Power
        ///// </summary>
        //[TestMethod]
        //public void PowerTest()
        //{
        //    double[] a = null; // TODO: Initialize to an appropriate value
        //    Polynom target = new Polynom(a); // TODO: Initialize to an appropriate value
        //    int actual;
        //    actual = target.Power;
        //    Assert.Inconclusive("Verify the correctness of this test method.");
        //}

        /// <summary>Тестирование унарного оператора отрицания -P</summary>
        [TestMethod, Priority(1), Description("Тестирование унарного оператора отрицания -P")]
        public void op_UnaryNegationTest()
        {
            Action<Polynom, Polynom> test =
                (PP, QQ) =>
                {
                    Assert.AreEqual(PP.Power, QQ.Power, "Порядки полиномов P = {0} и Q = {1} не совпадают!", PP, QQ);
                    PP.Zip(QQ, (p, q) => new { p, q })
                        .Foreach((z, i) => Assert.AreEqual(z.p, -z.q,
                                                            "Для полинома P = {0} значение {1} коэффициента P[{1}] = {2} " +
                                                            "не равно инвертированному значению полинома Q = {3} : -Q[{1}] = {4}",
                                                            PP, i, z.p, QQ, -z.q));
                    Assert.IsTrue(PP.Equals(-QQ), "Невыполняется равенство P == Q, Если Q = -P для P = {0}, Q = {1}", PP, QQ);

                    GetRndDoubleVector(sigma: 10).Foreach(x => Assert.AreEqual(0, PP.Value(x) + QQ.Value(x), 1e-16));
                };

            var P = new Polynom(3, 5, 7);
            test(P, -P);

            P = GetRandomPolynom();
            test(P, -P);
        }

        /// <summary>Тестирование оператора разности двух полиномов</summary>
        [TestMethod, Priority(1), Description("Тестирование оператора разности двух полиномов")]
        public void op_SubtractionTest()
        {
            Action<Polynom, Polynom> test =
                (P, Q) =>
                {
                    var Z = P - Q;
                    Assert.AreEqual(Math.Max(P.Power, Q.Power), Z.Power,
                        "Степень полинома разности {3} = {6} не равна " +
                        "максимуму из степеней уменьшаемого {0} = {3} и вычитаемого {1} = {4} полиномов",
                        P, Q, Z, P.Power, Q.Power, Z.Power);
                    GetRndDoubleVector(1000, sigma: 100)
                        .Select(x => new { yP = P.Value(x), yQ = Q.Value(x), yZ = Z.Value(x) })
                        .Select(v => new { expected = v.yP - v.yQ, actual = v.yZ })
                        .Foreach(v => Assert.AreEqual(0, (v.expected - v.actual) / v.expected, 1e-10));
                };

            var p = new Polynom(3, 5, 7);
            var q = new Polynom(2, 3, 5);
            Assert.AreEqual(new Polynom(1, 2, 2), p - q);
            test(p, q);
            test(GetRandomPolynom(4), GetRandomPolynom(5));
            test(GetRandomPolynom(), GetRandomPolynom());

        }

        /// <summary>
        /// A test for op_Multiply
        /// </summary>
        [TestMethod]
        public void op_MultiplyTest()
        {
            var p = Polynom.Random(5);
            var q = Polynom.Random(7);
            var z = p * q;

            var x = GetRndDoubleSigmaMu();

            Assert.AreEqual(p.Value(x) * q.Value(x), z.Value(x), 0.0001);
        }
    }
}
